home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Languguage OS 2
/
Languguage OS II Version 10-94 (Knowledge Media)(1994).ISO
/
language
/
embedded
/
68hc11
/
servo8_a.txt
< prev
next >
Wrap
Text File
|
1994-10-20
|
13KB
|
370 lines
*
* Author: Alan Kilian
*
* Title : HC11 RC Servo routines
*
* File Name : servo8.asm
*
* Description : This program demonstrates the use of a HC11 processor
* to control 8 Model airplane type servos. The servos
* are updated every 20 milliseconds and their individual
* pulses do not jitter more than 1 microsecond.
*
* History : 02/11/93 Created. Tested out one pulse.
* 02/15/93 First cut of eight pulses.
* 02/16/93 Getting eight good clean pulses.
* 02/17/93 Added sort routine and tons of comments.
* 02/17/93 Added user input routine.
* 02/18/93 Debugged the whole thing and it was no fun at all
* 02/18/93 Removed all the printing routines and debug stuff.
* 02/20/93 Removed FCB from RAM area. Don't do that.
*
* Use: Send ASCII characters over the serial line to control the servos
* The format is list this:
* s0000 sets servo 00 to pulse width $00 which is the minimum
* available pulse width.
* s0080 sets servo 00 to pulse width $80 HEX which is the
* middle width pulse
* s00FF sets servo 00 to pulse width $FF HEX which is the
* largest available pulse width
* s0100 servo one
* r resets all servos to a pulse width $80
*
* Note : This program is written for the MC68HC811E2 processor running
* in single-chip mode. It is designed for use with a processor
* running with an 8 mHz crystal. If you are using a different
* crystal frequency you will need to re-compute all of the
* timing values in this code.
*
* The structure, serial I/O and command processor portions of
* this program evolved from the program HEXLOB40 written by
* Fred Martin and Randy Sargent and we thank them greatly.
****************************************************************************
* Two-Byte Registers (High,Low -- Use Load & Store Double to access)
TOC2 EQU $1018 ; Timer Output Compare register 2
* One-Byte Registers
PORTB EQU $1004 ; PORT B data register
TCTL1 EQU $1020 ; Timer ConTroL register 1
TCTL2 EQU $1021 ; Timer ConTroL register 2
TMSK1 EQU $1022 ; Timer interrupt MaSK register 1
TFLG1 EQU $1023 ; Timer interrupt FLaG register 1
RTFLG1 EQU $23 ; Relative to $1000 Timer interrupt FLaG register 1
TMSK2 EQU $1024 ; Timer interrupt MaSK register 2
TFLG2 EQU $1025 ; Timer interrupt FLaG register 2
SPCR EQU $1028 ; SPI Control Register
BAUD EQU $102B ; SCI Baud Rate Control Register
SCCR2 EQU $102D ; SCI Control Register 2
SCSR EQU $102E ; SCI Status Register (Actual location)
SCDR EQU $102F ; SCI Data Register (Actual location)
* Masks for serial port
PORTD_WOM EQU $20 ; Wire-OR mode
BAUD1200 EQU $B3 ; 1200 Baud what else?
BAUD9600 EQU $B0 ; 9600 Baud what else?
TRENA EQU $0C ; Transmit, Receive ENAble
RDRF EQU $20 ; Receive Data Register Full
TDRE EQU $80 ; Transmit Data Register Empty
********************************************************************************
* zero page RAM definitions. Do not use FCB here. It will stomp EEBOOT20.
********************************************************************************
ORG $00 ; The beginning of RAM
values RMB 8 ; The place where the unsorted values live
servnum RMB 2 ; A safe place for the servo number
smasks RMB 8 ; A safe place to put sorted masks
svalues RMB 8 ; A safe place to put sorted values
dvalues RMB 8 ; A safe place to put sorted delta values
newvals RMB 1 ; Alert the interrupt routine about new values
**********************************************************************
* MAIN CODE *
**********************************************************************
ORG $F800 ; $F800 is the beginning of EEPROM
* ; on a MC68HC811E2 processor
Start:
LDS #$00FF ; Set stack at the top of ram
BCLR SPCR PORTD_WOM ; initialize serial port
LDAA #BAUD9600 ; turn off wire-or mode
STAA BAUD ; Set the port for 9600 baud
LDAA #TRENA
STAA SCCR2 ; Enable the serial subsystem
LDAA #1
STAA newvals ; Set newvals the first time through
LDAA #0
STAA servnum ; Zero out the high byte of servnum
LDX #0 ; set the servos to the middle
LDAA #$80 ; of the range of pulse widths
Stlp:
STAA values,X
INX
CPX #7
BLS Stlp
********************************************************************************
* set up interrupts
* OC2: once every 20 milliseconds
********************************************************************************
LDAA #%01000000 ; Set up the OC2 interrupt to generate
STAA TCTL1 ; an interrupt once every
STAA TFLG1 ; 20 milliseconds
STAA TMSK1
CLI ; Turn on interrupts
mainloop:
LDAA #$0D ; return character
JSR putchar
LDAA #$0A ; linefeed character
JSR putchar
LDAA #'> ; prompt character
JSR putchar
cmd_get_type:
JSR getchar ; Keep getting chars until you get one
CMPA #'a ; that is alphabetic.
BLO cmd_get_type ; If the char was less than 'a' ignore
CMPA #'s ; The command is an 's' set a new value
BEQ set_servo_val
CMPA #'r ; the command is an 'r' reset values
BEQ reset_servos
BRA mainloop ; Do it all over again
set_servo_val:
JSR getbyte ; Get the servo number
STAA servnum+1
LDX servnum ; And get it into the X register
JSR getbyte ; Get the pulse width
STAA values,X ; And save it in the values list
LDAA #1 ; Alert the interrupt handler that there are
STAA newvals ; new values in the values list
BRA mainloop ; Go back to the command loop
reset_servos
LDX #0
rtop:
LDAA #$80 ; Reset the servo values to a nice middle
STAA values,X ; pulse width
INX
CPX #7
BLS rtop
BRA mainloop ; Go back to the command loop
getbyte:
JSR getchar ; read 2 chars from the serial port and change
CMPA #'A ; them into a one byte value in accumulator A
BLO hibyteok ; This routine destroys accumulator B
SUBA #'A-10 ; so watch it.
hibyteok
ASLA
ASLA
ASLA
ASLA
TAB
JSR getchar ; Get the second character which is the least
CMPA #'A ; significant 4 bits of the value
BLO lobyteok
SUBA #'A-10
lobyteok
ANDA #$0f
ABA
RTS
getchar:
LDAA SCSR ; Read a character from the serial port and put
ANDA #RDRF ; it in accumulator A
BEQ getchar
LDAA SCDR
ANDA #$7f
RTS
putchar:
LDAB SCSR ; Send the character in accumulator A out the
ANDB #TDRE ; serial port.
BEQ putchar ; This routine destroys accumulator B
STAA SCDR ; so watch it.
RTS
oc2int:
LDD #40000 ; Once every 20 milliseconds
ADDD TOC2 ; We need to generate an interrupt
STD TOC2
LDX #$1000
BCLR RTFLG1,X %10111111 ; clear OC2 for next compare
********************************************************************************
* This section is timing critical. If you need to change anything in the
* oc2st loop you MUST make sure to get the delay in the oc2dn loop identical.
* There is a delay of 675 clocks to set the minimum pulse width. If you want
* to have a longer or shorter minimum pulse width you can change this value.
* The value 675 is derived as follows: The oc2st loop takes 39 clocks to
* start each pulse. Therefore it takes 7*39 clocks from the start of the first
* pulse until the loop completes. The oc2dn loop takes 32 clocks until it stops
* the first pulse if that servo's values array holds a zero. This means that if
* we want a 950 clock (475 microsecond) minimum pulse we need to delay
* 950 - (7*39) - 32 = 645 clocks between the end of oc2st and the beginning
* of oc2dn. A DECA/BNE loop takes 5 clocks so we load A with 645/5 = 129.
* Now the LDAA #129 adds 2 clocks to the delay but do you REALLY care about
* a 2 clock difference in the desired minimum pulse width and the actual
* minimum pulse width?
* Do NOT replace it with an interrupt driven delay since this would introduce
* an unpredictable interrupt latency of as much as 41 clocks. This much change
* in the pulse width will almost surely cause the servos to jitter.
* Currently the oc2st loop takes 39 clocks as does the oc2dn loop.
* The pulse width changes 13 clocks for every count in the values array.
* This gives you a 13*256*500nanoSecond = 1664 microsecond change in pulse
* width from a zero to FF in the values array. If you add the 950 clock minimum
* pulse width to that you can produce pulses from 475 microseconds through
* 2139 microseconds with a resolution of 6.5 microseconds. Pretty good huh?
* The numbers after the semicolon are the number of clocks the instruction
* takes to execute. Branches take the same time even if the branch is not taken.
* One clock is 500 nanoseconds if you are using an 8 mHz crystal. If you are
* not using an 8 mHz crystal then all these timings are wrong. Too bad.
********************************************************************************
oc2st:
LDX #0 ;3 Start at the shortest pulse-width
oc2st1:
LDAB smasks,X ;4 Figure out which servo it is
ORAB PORTB ;4
STAB PORTB ;4 Turn it on
LDAA #3 ;2 We need to blow 15 clocks
oc2st2:
DECA ;2
BNE oc2st2 ;3
INX ;3 Go to the next servo
CPX #7 ;4
BLS oc2st1 ;3 Not done, do another one.
LDAA #129 ;2 Now, delay the minimum pulse-width
delay1:
DECA ;2 Which is 645 clocks
BNE delay1 ;3
oc2dn:
LDX #0 ;3 Now start turning off the servo pulses
oc2dn1:
LDAB smasks,X ;4 Figure out which servo is first
EORB PORTB ;4 Figure out what to store in PORTB
LDAA dvalues,X ;4 Get this servos desired pulse width
oc2dn2:
NOP ;2
NOP ;2
NOP ;2
NOP ;2
DECA ;2 And delay 13 clocks per unit
BNE oc2dn2 ;3
STAB PORTB ;4 Finally turn off the pulse
INX ;3 Go to the next servo
CPX #7 ;4 Are we on the last servo?
BLS oc2dn1 ;3 No, do another one
LDAA newvals ; Next see if there is a new values list
CMPA #0
BEQ done ; If not, we are done
LDAA #0
STAA newvals ; Zero out the new values indicator
LDX #0
LDAB #$01
oc2cp:
LDAA values,X ; Copy the values array
STAA svalues,X ; Into the svalues array
STAB smasks,X ; (Save a proper mask for this port)
ASLB ;
INX ; So that we can sort them without
CPX #7 ; worrying about getting new user
BLS oc2cp ; values in the values array
********************************************************************************
* Sort the svalues list so that the lowest pulse widths are at the beginning of
* the list. Also make sure to swap around the smasks list so that the proper
* masks stay with the values.
********************************************************************************
oc2sort:
LDY #0 ; Y is our "times through the list" counter
sorttop:
LDX #0 ; X is our pointer into the list of values
sortlp:
LDAA svalues,X ; Get a value
LDAB svalues+1,X ; Get the following value
CBA ; Compare them
BLS noswap ; The first one is lower. No not swap them
STAA svalues+1,X ; Swap the two values
STAB svalues,X
LDAA smasks,X ; Also swap the bit masks
LDAB smasks+1,X
STAA smasks+1,X
STAB smasks,X
noswap:
INX ; Go to the next entry in the list
CPX #6 ; 6 is the second-to-last item and we are done
BLS sortlp ; Not done yet, do another pair of items
INY
CPY #6 ; We only need to sort 7 times so 6 is right
BLS sorttop
LDX #0 ; Now convert from pulse width values
LDAA svalues,X
INCA ; (Fix up for the delay loop)
STAA dvalues,X
oc2con:
LDAA svalues+1,X ; Into delta values so that we can simply
SUBA svalues,X ; delay the time left for each pulse
INCA ; (Fix up for the delay loop)
STAA dvalues+1,X
INX
CPX #6
BLS oc2con
done:
RTI ; Done, now that didn't hurt too much did it?
BadInt RTI ; Set all unused vectors here
Org $FFC0 ; Where the interrupt vectors are
FDB BadInt * $FFC0 ; Reserved
FDB BadInt * $FFC2 ; Reserved
FDB BadInt * $FFC4 ; Reserved
FDB BadInt * $FFC6 ; Reserved
FDB BadInt * $FFC8 ; Reserved
FDB BadInt * $FFCA ; Reserved
FDB BadInt * $FFCC ; Reserved
FDB BadInt * $FFCE ; Reserved
FDB BadInt * $FFD0 ; Reserved
FDB BadInt * $FFD2 ; Reserved
FDB BadInt * $FFD4 ; Reserved
FDB BadInt * $FFD6 ; SCI Serial System
FDB BadInt * $FFD8 ; SPI Serial Transfer Complete
FDB BadInt * $FFDA ; Pulse Accumulator Input Edge
FDB BadInt * $FFDC ; Pulse Accumulator Overflow
FDB BadInt * $FFDE ; Timer Overflow
FDB BadInt * $FFE0 ; In Capture 4/Output Compare 5 (TI4O5)
FDB BadInt * $FFE2 ; Timer Output Compare 4 (TOC4)
FDB BadInt * $FFE4 ; Timer Output Compare 3 (TOC3)
FDB oc2int * $FFE6 ; Timer Output Compare 2 (TOC2)
FDB BadInt * $FFE8 ; Timer Output Compare 1 (TOC1)
FDB BadInt * $FFEA ; Timer Input Capture 3 (TIC3)
FDB BadInt * $FFEC ; Timer Input Capture 2 (TIC2)
FDB BadInt * $FFEE ; Timer Input Capture 1 (TIC1)
FDB BadInt * $FFF0 ; Real Time Interrupt (RTI)
FDB BadInt * $FFF2 ; External Pin or Parallel I/O (IRQ)
FDB BadInt * $FFF4 ; Pseudo Non-Maskable Interrupt (XIRQ)
FDB BadInt * $FFF6 ; Software Interrupt (SWI)
FDB BadInt * $FFF8 ; Illegal Opcode Trap ()
FDB BadInt * $FFFA ; COP Failure (Reset) ()
FDB BadInt * $FFFC ; COP Clock Monitor Fail (Reset) ()
FDB Start * $FFFE ; /RESET
END